package Rangel.firebrigade.search;

import Rangel.common.RangelConstants;
import Rangel.common.world.entity.Path;
import Rangel.common.world.entity.RangelBuilding;
import Rangel.common.world.entity.RangelRoad;
import Rangel.common.world.helper.CivilianHelper;
import Rangel.common.world.helper.RangelWorldHelper;
import Rangel.common.world.tools.Util;
import adf.agent.info.AgentInfo;
import adf.agent.info.WorldInfo;
import rescuecore2.standard.entities.*;
import rescuecore2.worldmodel.EntityID;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class CivilianSearchDecisionMaker {
    private Set<EntityID> shouldDiscoverBuildings;
    private Set<EntityID> shouldFindCivilians;
    private RangelWorldHelper world;
    private AgentInfo agentInfo;
    private WorldInfo worldInfo;

    public CivilianSearchDecisionMaker(RangelWorldHelper world, AgentInfo ai, WorldInfo wi) {
        this.world = world;
        agentInfo = ai;
        worldInfo = wi;
    }

    public void initialize() {
        shouldFindCivilians = new HashSet<>();
        shouldDiscoverBuildings = new HashSet<>();
    }

    public void update() {
        removeZeroBrokennessBuildings();
        removeBurningBuildings();
        removeVisitedBuildings();

        setShouldFindCivilians();
        setShouldDiscoverBuildings();
        if (!(agentInfo.me() instanceof PoliceForce)) {
            removeUnreachableBuildings();
        }
        updateCivilianPossibleValues();
    }

    private void removeUnreachableBuildings() {
        List<EntityID> toRemove = new ArrayList<>();
        RangelBuilding RangelBuilding;
        for (EntityID bID : shouldDiscoverBuildings) {
            RangelBuilding = world.getRangelBuilding(bID);
            if (RangelBuilding.unVisitable()) {
                toRemove.add(bID);
            }
        }
        shouldDiscoverBuildings.removeAll(toRemove);
    }

    private void removeVisitedBuildings() {
        shouldDiscoverBuildings.removeAll(world.getVisitedBuildings());
    }

    Area getNextArea() {
        EntityID greatestValue = null;
        double maxValue = 0;
        RangelBuilding RangelBuilding;
        for (EntityID buildingID : shouldDiscoverBuildings) {
            RangelBuilding = world.getRangelBuilding(buildingID);
            double value = RangelBuilding.getCivilianPossibleValue();
            if (value > maxValue) {
                maxValue = value;
                greatestValue = buildingID;
            }
        }

        if (greatestValue == null) {
            return null;
        }

        return world.getEntity(greatestValue, Area.class);
    }


    Area getBetterTarget(Area presentTarget) {
        Area bestArea = getNextArea();
        RangelBuilding presentBuildingTarget = world.getRangelBuilding(presentTarget.getID());
        if (bestArea instanceof Building) {
            RangelBuilding bestBuilding = world.getRangelBuilding(bestArea.getID());
            if (bestBuilding.getCivilianPossibleValue() >= presentBuildingTarget.getCivilianPossibleValue() * 2) {
                return bestArea;
            }
        }
        return null;
    }

    private void updateCivilianPossibleValues() {
        RangelBuilding RangelBuilding;
        for (EntityID bID : shouldDiscoverBuildings) {
            RangelBuilding = world.getRangelBuilding(bID);
            double civilianPossibleValue = RangelBuilding.getCivilianPossibly().size();
            if (civilianPossibleValue != 0) {
                StandardEntity position = world.getSelfPosition();
                double distance = Util.distance(worldInfo.getLocation(agentInfo.getID()), worldInfo.getLocation(RangelBuilding.getSelfBuilding()));
                double timeToArrive = distance / RangelConstants.MEAN_VELOCITY_OF_MOVING;
                if (timeToArrive > 0) {
                    civilianPossibleValue /= timeToArrive;
                    if (position instanceof Road) {
                        RangelRoad RangelRoad = world.getRangelRoad(position.getID());
                        for (Path path : RangelRoad.getPaths()) {
                            if (path.getBuildings().contains(RangelBuilding.getSelfBuilding())) {
                                civilianPossibleValue *= 2;
                            }
                        }
                    }
                } else {
                    civilianPossibleValue = 0;
                }
            }
            RangelBuilding.setCivilianPossibleValue(civilianPossibleValue);
        }
    }


    private void removeBurningBuildings() {
        Building building;
        Set<EntityID> toRemove = new HashSet<>();
        for (EntityID buildingID : shouldDiscoverBuildings) {
            building = (Building) worldInfo.getEntity(buildingID);
            if (building.isFierynessDefined() && building.getFieryness() > 0 && building.getFieryness() != 4) {
                toRemove.add(buildingID);
            }
        }
        shouldDiscoverBuildings.removeAll(toRemove);
    }

    private void setShouldFindCivilians() {
        shouldFindCivilians.clear();
        for (EntityID civId : world.getAllCivilians()) {
            StandardEntity civEntity = world.getEntity(civId);

            Civilian civilian = (Civilian) civEntity;
            if (civilian == null || !civilian.isPositionDefined()) {
                shouldFindCivilians.add(civId);
            }
        }
    }

    private void setShouldDiscoverBuildings() {
        CivilianHelper civilianHelper = world.getHelper(CivilianHelper.class);
        Set<EntityID> possibleBuildings;
        RangelBuilding RangelBuilding;
        shouldDiscoverBuildings.clear();
        for (EntityID civId : shouldFindCivilians) {
            possibleBuildings = new HashSet<>(civilianHelper.getPossibleBuildings(civId));
            for (EntityID possibleBuildingID : possibleBuildings) {
                RangelBuilding = world.getRangelBuilding(possibleBuildingID);
                RangelBuilding.addCivilianPossibly(civId);
                shouldDiscoverBuildings.add(possibleBuildingID);
            }
        }
    }

    private void removeZeroBrokennessBuildings() {
        Building building;
        List<EntityID> toRemove = new ArrayList<>();
        for (EntityID buildingID : shouldDiscoverBuildings) {
            building = (Building) worldInfo.getEntity(buildingID);
            if (building.isBrokennessDefined() && building.getBrokenness() == 0) {
                toRemove.add(buildingID);
            }
        }
        shouldDiscoverBuildings.removeAll(toRemove);
    }
}